﻿using Abide_AddOn_API;
using Abide_AddOn_API.Classes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace Abide
{
    static class Program
    {
        private static Splash splashScreen = null;
        private static Classes.Dropbox UpdateInfo = null;
        private static List<Classes.AddOnManifest> AddOnManifests = null;
        private static List<Classes.AddOnManifest> OnlineManifests = null;
        private static List<string> DirectLoadAssemblies = null;
        private static bool Splash = Properties.Settings.Default.ShowSplash;
        private static bool SafeMode = false;
        private static bool HideLog = true;
        private static bool SilentDownload = false;

        public static bool ForceUpdate = false;
        public static Forms.ConsoleLog Log;
        public static string ThemesPath;
        public static string AddOnsPath;
        public static string[] Maps;
        public static string[] Files;
        public static DebugXbox Xbox;
        public static event EventHandler XboxChanged;
        private const string DropboxUpdateUrl = "https://dl.dropboxusercontent.com/u/238557949/Potential%20Software/Abide";
        private const string UpdateName = "Abide";
        private static Form main;
        private static bool AddOnUpdate = false;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        private static void Main(string[] Args)
        {
            //Prepare...
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            //Prepare
            Prepare();

            //Handle Arguments
            if (HandleArguments(Args))
                return;

            //Show?
            if (!HideLog)
                Log.Show();

            //Create...
            splashScreen = new Splash();

            //Check for Update
            UpdateInfo = new Classes.Dropbox(DropboxUpdateUrl, UpdateName);
            UpdateInfo.BeginLoad(LoadUpdateInfo, UpdateInfo);

            //Check...
            if (Splash)
            {
                splashScreen.Show();
                splashScreen.BringToFront();
                splashScreen.Refresh();
            }
            
            //Continue...
            Continue();
        }

        private static void LoadUpdateInfo(IAsyncResult ar)
        {
            //Try...
            try
            {
                //...
                string UpdaterFile = Path.Combine(Application.StartupPath, "Abide Updater.exe");

                //Check if successful...
                if (UpdateInfo.EndLoad(ar))
                {
                    //Compare
                    if (UpdateInfo.Latest.Version > Assembly.GetExecutingAssembly().GetName().Version || ForceUpdate)
                        using (UpdateDialog updateDialog = new UpdateDialog(UpdateInfo.Latest, UpdateInfo.UpdaterUrl,
                            UpdateInfo.Date, UpdaterFile))
                        {
                            //Show...
                            updateDialog.ShowDialog();

                            //Check...
                            if (updateDialog.DownloadCompleted)
                            {
                                Classes.Security.ExecuteElevated(UpdaterFile, Environment.CurrentDirectory, new string[] { Application.StartupPath });
                                Application.Exit();
                            }
                        }
                }
            }
            catch { }
        }
        private static void Continue()
        {
            //Load AddOns
            PrepareAddOns();

            //Start
            main = new Main();
            Application.Run(main);
        }

        //Preparation
        private static void Prepare()
        {
            //Upgrade Settings
            if (Properties.Settings.Default.UpdateRequired)
            {
                Properties.Settings.Default.UpdateRequired = false;
                Properties.Settings.Default.Upgrade();
                Properties.Settings.Default.Save();
            }

            //Prepare Debug Xbox
            Xbox = new DebugXbox();

            //Initialize...
            Settings.CreateKeys();

            //Setup
            AddOnsPath = Settings.AddOnsPath;
            ThemesPath = Settings.ThemesPath;

            //Redirect Log
            Log = new Forms.ConsoleLog();
            Console.SetOut(Log.Writer);
            Console.WriteLine("Redirecting Console Output to Console Log Window.");

            //Delete Updater
            string Updater = Path.Combine(Application.StartupPath, "Abide Updater.exe");
            if (File.Exists(Updater))
            {
                Console.WriteLine("Updater Found... Deleting...");
                Thread.Sleep(500);
                File.Delete(Updater);
            }

            //Check Settings
            if (Properties.Settings.Default.FirstRun)
            {
                using (Forms.AssociationForm af = new Forms.AssociationForm())
                    af.ShowDialog();

                Properties.Settings.Default.FirstRun = false;
                Properties.Settings.Default.Save();
            }
        }
        private static bool HandleArguments(string[] Args)
        {
            //Check Arguments
            bool exit = false;
            bool expRestart = false;
            List<string> Maps = new List<string>();
            List<string> Files = new List<string>();
            DirectLoadAssemblies = new List<string>();
            string UpdaterFile = Path.Combine(Application.StartupPath, "Abide Updater.exe");
            for (int Index = 0; Index < Args.Length; Index++)
            {
                string Argument = Args[Index];
                int Next = Index + 1;
                int ProgID = Next + 1;
                int Description = ProgID + 1;
                int Icon = Description + 1;

                switch (Argument)
                {
                    case "-safe":
                    case "-s": SafeMode = true; Console.WriteLine("Enabling Safe Mode..."); break;
                    case "-log": Log.LogToFile(Args[Next]); break;
                    case "-nosplash": Splash = false; break;
                    case "-d": DebugMode(); break;  //Debug Mode
                    case "-ap": if (Next < Args.Length && Directory.Exists(Args[Next])) AddOnPath(Args[Next]); break;   //Specified AddOns Path
                    case "-tp": if (Next < Args.Length && Directory.Exists(Args[Next])) ThemePath(Args[Next]); break;   //Specified Themes Path
                    case "-register":   //Register File Type with Abide Type
                        if (Icon < Args.Length)
                            Classes.Registration.Register(Args[Next], Args[ProgID], Args[Description], Args[Icon], string.Empty);
                        exit = true; expRestart |= true; break;
                    case "-delete":     //Delete Directory or File
                        if (Next < Args.Length)
                            if (File.Exists(Args[Next]))
                                File.Delete(Args[Next]);
                            else if (Directory.Exists(Args[Next]))
                                Directory.Delete(Args[Next], true);
                        break;
                    case "-sud":
                        //Download Update Info
                        UpdateInfo = new Classes.Dropbox(DropboxUpdateUrl, UpdateName);
                        UpdateInfo.Load();

                        //Download Updater
                        var downloader = new System.Net.WebClient();
                        downloader.DownloadFile(new Uri(UpdateInfo.UpdaterUrl), UpdaterFile);

                        //Exit
                        exit = true;
                        break;
                    case "-u": ForceUpdate = true; break;
                    case "-createupdate":
                    case "-cu":
                        CreateAddOnUpdate(Args[Next], Args[Next + 1], Args[Next + 2], Args[Next + 3]);
                        Index += 3;
                        exit = true;
                        break;
                    case "-la": DirectLoadAssemblies.Add(Args[Next]); break;
                    case "-ua": AddOnUpdate = true; break;
                    case "-showlog": HideLog = false; break;

                    default:
                        if (File.Exists(Argument))
                        {
                            //Get Info
                            FileInfo Info = new FileInfo(Argument);
                            string addOnPath = Path.Combine(AddOnsPath, Info.Name.Replace(Info.Extension, string.Empty));

                            switch (Info.Extension.Remove(0, 1))
                            {
                                case "aao":
                                    //Extract
                                    Classes.ZipOperations.ExtractZipFile(Argument, string.Empty, addOnPath);

                                    //Installed
                                    MessageBox.Show(string.Format("{0} Installed", Info.Name), "Installed", MessageBoxButtons.OK, MessageBoxIcon.Information);

                                    //Exit
                                    exit = true;
                                    break;

                                case "map":
                                    Maps.Add(Argument);
                                    break;

                                default:
                                    Files.Add(Argument);
                                    break;
                            }
                        }
                        break;
                }
            }

            //Set
            Program.Maps = Maps.ToArray();
            Program.Files = Files.ToArray();

            //Restart Explorer?
            if (expRestart)
                if (MessageBox.Show("It's highly reccomended that Explorer is restarted. Would you like to do that now?",
                                "Restart Explorer?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
                {
                    System.Diagnostics.Process[] Explorer = System.Diagnostics.Process.GetProcessesByName("explorer");
                    if (Explorer.Length > 0)
                        Explorer[0].Kill();
                }

            //Exit
            return exit;
        }
        
        //Arguments
        private static void DebugMode()
        {
            Console.WriteLine("Loading in Debug Mode...");
            System.Diagnostics.Debugger.Launch();
        }
        private static void AddOnPath(string addonPath)
        {
            Console.WriteLine("Loading using AddOns Path: \"{0}\"", addonPath);
            AddOnsPath = addonPath;
        }
        private static void ThemePath(string themePath)
        {
            Console.WriteLine("Loading using Themes Path: \"{0}\"", themePath);
            ThemesPath = themePath;
        }
        private static void CreateAddOnUpdate(string package, string assembly, string outputDirectory, string packageUrl)
        {
            //Load AssemblyName
            AssemblyName name = AssemblyName.GetAssemblyName(assembly);
            FileInfo Package = new FileInfo(package);

            //Get Safe Name
            string safeName = name.Name.Trim().Replace(' ', '_').Replace('<', '_').Replace('>', '_').Replace('?', '_');

            //Copy file
            File.Copy(package, Path.Combine(outputDirectory, string.Format("{0}.aao", name.Name)), true);

            //Write
            System.Xml.XmlWriterSettings Settings = new System.Xml.XmlWriterSettings();
            Settings.Indent = true;
            Settings.Encoding = System.Text.Encoding.UTF8;
            Settings.CloseOutput = true;

            //Create
            using (FileStream fs = new FileStream(Path.Combine(outputDirectory, "Update.xml"), FileMode.Create))
            {
                //Create Writer
                System.Xml.XmlWriter Writer = System.Xml.XmlWriter.Create(fs, Settings);

                //Write Document
                Writer.WriteStartDocument();

                //Write Main
                Writer.WriteStartElement(safeName);

                //Write Latest
                Writer.WriteStartElement("Latest");
                Writer.WriteValue(name.ToString());
                Writer.WriteEndElement();

                //Write Date
                Writer.WriteStartElement("Date");
                Writer.WriteValue(DateTime.Now);
                Writer.WriteEndElement();

                //Write Package
                Writer.WriteStartElement("Updater");
                Writer.WriteValue(packageUrl);
                Writer.WriteEndElement();

                //Write Notes
                Writer.WriteStartElement("Notes");
                Writer.WriteEndElement();

                Writer.WriteEndElement();

                //End
                Writer.WriteEndDocument();

                //Close
                Writer.Close();
            }
        }

        public static void CloseSplash()
        {
            //Exit
            splashScreen.Close();
            splashScreen.Dispose();
        }
        public static void ChangeXbox(object sender)
        {
            //Throw Event
            XboxChanged?.Invoke(sender, new EventArgs());
        }
        private static void PrepareAddOns()
        {
            //Prepare
            List<IAsyncResult> results = new List<IAsyncResult>();
            AddOnManifests = new List<Classes.AddOnManifest>();
            OnlineManifests = new List<Classes.AddOnManifest>();
            AssemblyName Name = null;
            AddOnFactory Factory = null;

            //Create?
            if (!Directory.Exists(AddOnsPath))
                Directory.CreateDirectory(AddOnsPath);

            //Safe Mode (Load AddOn Manifests only)
            if (SafeMode)
            {
                //Get AddOn Folders...
                foreach (string Folder in Directory.GetDirectories(AddOnsPath))
                {
                    //Get Safe Name
                    string safeName = Path.GetFileName(Folder);
                    string manifestFileName = Path.Combine(Folder, "Manifest.xml");

                    //Read Manifest
                    if (File.Exists(manifestFileName))
                    {
                        //Read
                        Classes.AddOnManifest Manifest = new Classes.AddOnManifest();
                        Manifest.Load(manifestFileName);
                        Manifest.Root = Folder;

                        //Get Assembly Name
                        Name = AssemblyName.GetAssemblyName(Path.Combine(Folder, Manifest.PrimaryAssembly));
                        Manifest.AssemblyName = Name;

                        //Create
                        Factory = new AddOnFactory();
                        Factory.AddOnDirectory = Folder;

                        //Add
                        Classes.AbideAddOnManager.Collections.Add(Folder, Factory);
                    }
                }

                return;
            }

            //Try
            try
            {
                //Get AddOn Folders...
                foreach (string Folder in Directory.GetDirectories(AddOnsPath))
                {
                    //Get Safe Name
                    string safeName = Path.GetFileName(Folder);
                    string manifestFileName = Path.Combine(Folder, "Manifest.xml");

                    //Read Manifest
                    if (File.Exists(manifestFileName))
                    {
                        //Read
                        Classes.AddOnManifest Manifest = new Classes.AddOnManifest();
                        Manifest.Load(manifestFileName);
                        Manifest.Root = Folder;
                        AddOnManifests.Add(Manifest);

                        //Get Assembly Name
                        Name = AssemblyName.GetAssemblyName(Path.Combine(Folder, Manifest.PrimaryAssembly));
                        Manifest.AssemblyName = Name;

                        //Prepare Dropbox
                        Classes.Dropbox dropbox = new Classes.Dropbox(Manifest.UpdateInfoUrl, Manifest.GetSafeName());
                        Manifest.Dropbox = dropbox;

                        //Load Update Info
                        results.Add(dropbox.BeginLoad(UpdateCheckCallback, Manifest));
                    }
                }

                //Wait...
                ThreadPool.QueueUserWorkItem(WaitForUpdateCheck, results);

                //Check
                if (!AddOnUpdate)
                    LoadAddOns();
            }
            catch (Exception ex) { Console.WriteLine(ex.StackTrace); throw (ex); }
        }
        private static void UpdateCheckCallback(IAsyncResult ar)
        {
            //Get State
            Classes.AddOnManifest manifest = (Classes.AddOnManifest)ar.AsyncState;
            Classes.Dropbox dropBox = manifest.Dropbox;

            try
            {
                //End
                if (dropBox.EndLoad(ar))
                    OnlineManifests.Add(manifest);
            }
            catch { }
        }
        private static void WaitForUpdateCheck(object state)
        {
            //Get List
            List<IAsyncResult> Results = (List<IAsyncResult>)state;

            //Wait...
            bool Complete = Results.Count == 0;
            while (!Complete)
            {
                Thread.Sleep(1);

                if (main == null)
                    continue;

                Complete = true;
                foreach (var result in Results)
                    Complete &= result.IsCompleted;
            }

            //Wait...
            while (main == null || !main.IsHandleCreated)
                Thread.Sleep(1);

            //Invoke
            main.Invoke(new MethodInvoker(UpdateAddOns));
        }
        private static void UpdateAddOns()
        {
            //Prepare List
            List<Classes.AddOnManifest> Updates = new List<Classes.AddOnManifest>();

            //Check
            if (OnlineManifests.Count > 0)
            {
                //Loop
                foreach (var manifest in OnlineManifests)
                {
                    //Check
                    if (manifest.AssemblyName.Version < manifest.Dropbox.Latest.Version)
                        Updates.Add(manifest);
                }

                if (Updates.Count > 0)
                    if (AddOnUpdate)
                        using (Forms.AddOnUpdater Updater = new Forms.AddOnUpdater())
                        {
                            //Setup
                            Updater.Manifests = Updates.ToArray();

                            //Show
                            Updater.ShowDialog();
                        }
                    else
                        if (MessageBox.Show("AddOn Updates are available. Would you like to restart and download them?", "AddOn Updates",
                            MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                            Classes.Security.Restart("-ua");
            }

            //Check
            if (AddOnUpdate)
                LoadAddOns();
        }
        private static void LoadAddOns()
        {
            //Direct Load
            foreach (string assembly in DirectLoadAssemblies)
            {
                string AssemblyFileName = Path.Combine(AddOnsPath, assembly);
                FileInfo info = new FileInfo(AssemblyFileName);

                if (Classes.AbideAddOnManager.IsAddOn(AssemblyFileName))
                    Classes.AbideAddOnManager.DirectLoadAssembly(AssemblyFileName);
            }

            //Load AddOns
            foreach (var manifest in AddOnManifests)
            {
                //Check
                string AssemblyFileName = Path.Combine(manifest.Root, manifest.PrimaryAssembly);
                if (Classes.AbideAddOnManager.IsAddOn(AssemblyFileName))
                    Classes.AbideAddOnManager.LoadAssembly(manifest.Name, manifest.Root, AssemblyFileName);
            }
        }
    }
}